# Copyright 2009 Larry Resnik, Lasfraka-Elixir

#   This program is free software: you can redistribute it and/or modify
#   it under the terms of the GNU General Public License as published by
#   the Free Software Foundation, either version 3 of the License, or
#   (at your option) any later version.

#   This program is distributed in the hope that it will be useful,
#   but WITHOUT ANY WARRANTY; without even the implied warranty of
#   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#   GNU General Public License for more details.

#   You should have received a copy of the GNU General Public License
#   along with this program.  If not, see <http://www.gnu.org/licenses/>.


import pyglet
from physics import (Vector2D, circ_circ_collide, circ_rect_collide,
    rect_rect_collide, to_radians, to_degrees, center_anchor, get_distance,
    COLLIDE_TYPE_CIRC, COLLIDE_TYPE_RECT)
from math import acos, pi


class BulletBase(pyglet.sprite.Sprite):
    """A base class for all derived bullets.

    :parameters:
    collideType     An enumerated int that designates this bullet's type of
                collision to use in collision formulas.
                Default is COLLIDE_TYPE_CIRCLE
    damage          An integer for how much damage is dealt to a Thing when
                the bullet comes into contact with it.
    dead            A boolean representing if this bullet should be removed
                from the game during the current update cycle.
                Default is False
    grazed          A boolean representing if this bullet touched the player's
                hitbox or not. Default is False
    radius          An integer for the radius of the bullet's hitcircle.
    tolerance       An integer for how far from the edge of the screen the
                bullet can exist. Default is 128
    torque          A double for how much the bullet rotates in radians
                during each update. Default is 0.0
    """

    damage = 1
    dead = False
    grazed = False
    radius = 4
    tolerance = 128
    collideType = COLLIDE_TYPE_CIRC

    def __init__(self, handler, image, x=0.0, y=0.0, speed=1.0, angle=0.0,
        torque=0.0):
        """Bullet constructor

        :parameters:
        handler     A reference to the list that owns this bullet.
                SOON TO BE DEPRECATED.
        image       A pyglet image to generate this bullet's Sprite from.
        x           A double for the bullet's position on the x-axis.
                Default is 0.0
        y           A double for the bullet's position on the y-axis.
                Default is 0.0
        speed       A double for the distance the bullet travels over time.
                Default is 1.0
        angle       A double for the direction the bullet travels in radians.
                Default is 0.0 (or directly to the right)
        torque      A double for how much the bullet rotates in radians
                during each update. Default is 0.0
        """
        super(BulletBase, self).__init__(image, x, y, batch=handler.batch,
            group=handler.group)
        self.handler = handler
        self._speed = speed
        self.torque = torque
        self.direction = Vector2D()
        self.rotate(angle)

    def _update_speed(self):
        """Updates dx and dy.

        Calling set_speed() is preferred because other methods also call this.
        """
        self.__dx = self._speed * self.direction.x
        self.__dy = self._speed * self.direction.y

    def get_speed(self):
        return self._speed

    def set_speed(self, speed):
        self._speed = speed
        self._update_speed()

    def set_angle(self, radians):
        self.rotation = to_degrees(radians)
        self.direction.set_direction(radians)
        self._update_speed()

    def rotate(self, radians):
        self.set_angle(to_radians(self.rotation) + radians)

    def point_to(self, obj):
        radians = acos((obj.x - self.x) / get_distance(self, obj))
        if obj.y < self.y:
            radians = -radians
        self.set_angle(radians)

    def remove(self):
        """Remove this object from the game."""
        # Delete the sprite.
        self.delete()
        # Remove the bullet from the handler's array of bullets.
        self.handler.bullets.remove(self)

    def post_collide(self, damaged_obj):
        """Make this object react to having collided with something

        Most bullets will need only to be set to "dead" by this call.
        """
        self.dead = True

    def update(self, offsetX, offsetY):
        if self.torque:
            self.rotate(self.torque)
        self.x += self.__dx
        self.y += self.__dy
        if self.x < offsetX - self.tolerance or \
            self.x > offsetX + 320 + self.tolerance or \
            self.y < offsetY - 64 - self.tolerance or \
            self.y > offsetY + 240 + self.tolerance:
            self.dead = True

class BulletHandler(object):
    """This maintains groups of bullets.

    If it isn't given a batch, the handler will make its own. Feel free to call
    count, but don't change it please.
    """

    def __init__(self, associations, batch=None, group=None):
        self.associations = associations
        self.count = 0
        self.bullets = []
        if batch:
            self.batch = batch
        else:
            self.batch = pyglet.graphics.Batch()
        self.group = group

    def shoot(self, id, x=0, y=0, speed=1.0, angle=0.0, torque=0.0):
        # Note: The self argument is the handler (this caller) - not a typical
        # use of self.
        if id in self.associations:
            bullet = self.associations[id](self, x, y, speed, angle, torque)
        else:
            print('ID %s does not exist.' % id)
            bullet = BulletSmall(self, x, y, speed, angle, torque)
        self.bullets.append(bullet)
        self.count += 1
        return bullet

    def empty(self):
        """Removes all owned bullets from their batches and from memory."""
        for bullet in self.bullets[:]:
            bullet.remove()
        self.count = len(self.bullets)

    def hit_obj(self, bullet, obj):
        obj.hit(bullet)

    def update(self, objects, offsetX, offsetY):
        for bullet in self.bullets[:]:
            bullet.update(offsetX, offsetY)
        # Remove bullets that are off-screen or hit the player.
        for deadBullet in [bullet for bullet in self.bullets \
                           if bullet.dead]:
            deadBullet.remove()
            self.count -= 1
        # TODO: Implement broad-phase testing.
        for bullet in self.bullets[:]:
            for obj in objects:
                if bullet.collideType == COLLIDE_TYPE_CIRC:
                    if obj.collideType == COLLIDE_TYPE_CIRC and \
                        circ_circ_collide(bullet, obj):
                        self.hit_obj(bullet, obj)
                    elif obj.collideType == COLLIDE_TYPE_RECT and \
                        circ_rect_collide(bullet, obj):
                        self.hit_obj(bullet, obj)
                elif bullet.collideType == COLLIDE_TYPE_RECT:
                    if obj.collideType == COLLIDE_TYPE_CIRC and \
                        circ_rect_collide(obj, bullet):
                        self.hit_obj(bullet, obj)
                    elif obj.collideType == COLLIDE_TYPE_RECT and \
                        rect_rect_collide(bullet, obj):
                        self.hit_obj(bullet, obj)

    def draw(self):
        self.batch.draw()

class BulletSmall(BulletBase):
    image = None
    tolerance = 0

    def __init__(self, handler, x=0, y=0, speed=1.0, angle=0.0, torque=0.0):
        if BulletSmall.image is None:
            BulletSmall.image = pyglet.resource.image('bullet-s1.png')
            center_anchor(BulletSmall.image)
        super(BulletSmall, self).__init__(handler, BulletSmall.image,
            x, y, speed, angle, torque)

class BulletMarisa1(BulletBase):
    image = None
    tolerance = 16
    collideType = COLLIDE_TYPE_RECT
    damage = 2
    width = 3
    height = 3
    halfWidth = width / 2
    halfHeight = height / 2

    def __init__(self, handler, x=0, y=0, speed=4.0, angle=0.0, torque=0.0):
        if BulletMarisa1.image is None:
            BulletMarisa1.image = pyglet.resource.image('star-1.png')
            center_anchor(BulletMarisa1.image)
        super(BulletMarisa1, self).__init__(handler, BulletMarisa1.image,
            x, y, speed, angle, torque)


bulletAssociations = {
    'small':BulletSmall,
    'marisa1':BulletMarisa1
}
